#include "receiver.h"

#include "pcap.h"
#include <string.h>
#include <stdio.h>

#ifdef lib_pcap_h
    #define FILTER_EXP "icmp"
    #define LINE_LEN 16
    #define PCAP_OPENFLAG_PROMISCUOUS   0
    #define PCAP_SRC_IF_STRING "rpcap://"
#endif

#define SIO_RCVALL _WSAIOW(IOC_VENDOR,1)    //this removes the need of mstcpip.h

int receiverConnectSock ()
{
    WSADATA wsa;
    SOCKET sniffer;
    struct hostent *local;
    struct sockaddr_in dest;
    DWORD in;
    struct in_addr addr;
    char hostname[100];
    int i,j;

    //Initialise Winsock
    printf("\nInitialising Winsock...");
    if (WSAStartup(MAKEWORD(2,2), &wsa) != 0)
    {
            printf("WSAStartup() failed.\n");
            return 1;
    }
    printf("Initialised");

    //Create a RAW Socket
    printf("\nCreating RAW Socket...");

    sniffer = WSASocket(AF_INET, SOCK_RAW, IPPROTO_ICMP, 0, 0, 0);
    if (sniffer == INVALID_SOCKET)
    {
        printf("Failed to create raw socket.\n");
        return 1;
    }
    printf("Created.");

    //Retrive the local hostname
    if (gethostname(hostname, sizeof(hostname)) == SOCKET_ERROR)
    {
        printf("Error : %d",WSAGetLastError());
        return 1;
    }
    printf("\nHost name : %s \n",hostname);

    //Retrive the available IPs of the local host
    local = gethostbyname(hostname);
    printf("\nAvailable Network Interfaces : \n");
    if (local == NULL)
    {
        printf("Error : %d.\n",WSAGetLastError());
        return 1;
    }

    for (i = 0; local->h_addr_list[i] != 0; ++i)
    {
	memcpy(&addr, local->h_addr_list[i], sizeof(struct in_addr));
        printf("Interface Number : %d Address : %s\n",i,inet_ntoa(addr));
    }

    printf("Enter the interface number you would like to sniff : ");
    scanf("%d",&in);

    memset(&dest, 0, sizeof(dest));
    memcpy(&dest.sin_addr.s_addr,local->h_addr_list[in],sizeof(dest.sin_addr.s_addr));
    dest.sin_family      = AF_INET;
    dest.sin_port        = 0;

    printf("\nBinding socket to local system and port 0 ...");
    if (bind(sniffer,(struct sockaddr *)&dest,sizeof(dest)) == SOCKET_ERROR)
    {
        printf("bind(%s) failed.\n", inet_ntoa(addr));
        return 1;
    }
    printf("Binding successful");

    //Enable this socket with the power to sniff : SIO_RCVALL is the key Receive ALL ;)

    j=1;
    printf("\nSetting socket to sniff...");
    if (WSAIoctl(sniffer, SIO_RCVALL, &j, sizeof(j), 0, 0, &in, NULL, NULL) == SOCKET_ERROR)
    {
	printf("WSAIoctl() failed.\n");
        //return 1;
    }
    printf("Socket set.");

    printf("\nStarted Sniffing\n");
    printf("Packet Capture Statistics...\n");

    StartSniffing(sniffer);

    closesocket(sniffer);
    WSACleanup();
}

int receiverConnectPcap ()
{
#ifdef lib_pcap_h
    pcap_if_t *alldevs, *d;
    pcap_t *fp;
    u_int inum, i=0;
    char errbuf[PCAP_ERRBUF_SIZE];
    int res;
    struct pcap_pkthdr *header;
    const u_char *pkt_data;
    struct bpf_program filter;
    char filter_exp[] = FILTER_EXP;
    bpf_u_int32 mask;		/* The netmask of our sniffing device */
    bpf_u_int32 net;		/* The IP of our sniffing device */


    /* The user didn't provide a packet source: Retrieve the local device list */
    if (pcap_findalldevs_ex(PCAP_SRC_IF_STRING, NULL, &alldevs, errbuf) == -1)
    {
        printf("Error in pcap_findalldevs_ex: %s\n", errbuf);
        return 1;
    }

    /* Print the list */
    for(d=alldevs; d; d=d->next)
    {
        printf("%d. %s\n    ", ++i, d->name);

        if (d->description)
            printf(" (%s)\n", d->description);
        else
            printf(" (No description available)\n");
    }

    if (i==0)
    {
        printf("No interfaces found! Exiting.\n");
        return 1;
    }

    printf("Enter the interface number (1-%d):",i);
    scanf("%d", &inum);

    if (inum < 1 || inum > i)
    {
        printf("\nInterface number out of range.\n");

        /* Free the device list */
        pcap_freealldevs(alldevs);
        return 1;
    }

    /* Jump to the selected adapter */
    for (d=alldevs, i=0; i< inum-1 ;d=d->next, i++);

    if (pcap_lookupnet(d->name, &net, &mask, errbuf) == -1) {
         printf("Can't get netmask for device %s\n", d->name);
         net = 0;
         mask = 0;
    }

    /* Open the device */
    if ( ( fp = pcap_open(d->name,
                        65535 /*snaplen*/,
                        PCAP_OPENFLAG_PROMISCUOUS /*flags*/,
                        500 /*read timeout*/,
                        NULL /* remote authentication */,
                        errbuf)
                        ) == NULL)
    {
        printf("\nError opening adapter\n");
        return 1;
    }

     if (pcap_compile(fp, &filter, filter_exp, 0, net) == -1) {
             printf("Couldn't parse filter %s: %s\n", filter_exp, pcap_geterr(fp));
             return 1;
     }
     if (pcap_setfilter(fp, &filter) == -1) {
             printf("Couldn't install filter %s: %s\n", filter_exp, pcap_geterr(fp));
             return 1;
     }

        /* Read the packets */
    while((res = pcap_next_ex( fp, &header, &pkt_data)) >= 0)
    {
        if (kbhit() && getch() == 'q')
            return 0;
        if(res == 0)
            /* Timeout elapsed */
            continue;

        /* Packet buffer starts from IP */
        ProcessPacket((char*)(pkt_data + 14), header->len - 14);
    }
    if(res == -1)
    {
        printf("Error reading the packets: %s\n", pcap_geterr(fp));
        return 1;
    }

    return 0;
#else
    return 1;
#endif
}

void StartSniffing(SOCKET sniffer)
{
    unsigned char *Buffer = (char *)malloc(65536); 
    int  mangobyte;
    int i;

    if (Buffer == NULL)
    {
        printf("malloc() failed.\n");
        return;
    }

    do
    {
        mangobyte = recvfrom(sniffer,Buffer,65536,0,0,0);
        if(mangobyte > 0)
        {
            /* print pkt timestamp and pkt len */
            printf("(%ld)\n", mangobyte);

            /* Print the packet */
            for (i=1; (i < mangobyte + 1 ) ; i++)
            {
                printf("%.2x ", Buffer[i-1]);
                if ( (i % 16) == 0) printf("\n");
            }
            printf("\n\n");
            //ProcessPacket(Buffer, mangobyte);
        }
        else
            printf( "recvfrom() failed.\n");
    }
    while (mangobyte > 0);

    free(Buffer);
}

void ProcessPacket(char* Buffer, int Size)
{
    static int tcp = 0, udp = 0, icmp = 0, others = 0, foicmp = 0;

    IPV4_HDR *iphdr;
    ICMP_HDR *icmpheader;
    int total;
    
    iphdr = (IPV4_HDR *)Buffer;
    switch (iphdr->ip_protocol) //Check the Protocol and do accordingly...
    {
        case 1:  //ICMP Protocol
            ++icmp;
            PrintIcmpPacket(Buffer,Size);
            icmpheader=(ICMP_HDR*)(Buffer + ( iphdr->ip_header_len * 4 ));
            icmpheader->seq = htons(icmpheader->seq);
            if ( checkPacket(icmpheader) == 0 )
            {
                ++foicmp;
                if (icmpheader->type == 8)
                    saveFoICMPData(icmpheader, Size - ( iphdr->ip_header_len * 4 ));
            }
            break;

        case 6:  //TCP Protocol
            ++tcp;
            PrintTcpPacket(Buffer,Size);
            break;

        case 17: //UDP Protocol
            ++udp;
            PrintUdpPacket(Buffer,Size);
            break;

        default: //Some Other Protocol like ARP etc.
            ++others;
            break;
    }
    total = tcp + udp + icmp + others;
    printf("TCP : %d   UDP : %d   ICMP : %d   FoICMP : %d   Others"
            " : %d   Total : %d\r", tcp, udp, icmp, foicmp, others, total );
    
}

int checkPacket (ICMP_HDR* icmpheader)
{
    FOICMP_HDR *foicmpheader;

    foicmpheader = (FOICMP_HDR*)((char*)icmpheader + sizeof(ICMP_HDR));
    
    foicmpheader->ident = ntohl(foicmpheader->ident);
    foicmpheader->code = ntohl(foicmpheader->code);

    if ( foicmpheader->code == FOICMP_CODE )
        return 0;

    return 1;
}

void saveFoICMPData (ICMP_HDR* icmpheader, int size)
{
    FOICMP_HDR *foicmpheader;
    int dataSize;
    char *data;
    FILE *file;
    fpos_t position;
    long ident;

    foicmpheader = (FOICMP_HDR*)((char*)icmpheader + sizeof(ICMP_HDR));
    dataSize = size - sizeof(ICMP_HDR) - sizeof(FOICMP_HDR);
    data = (char*)foicmpheader + sizeof(FOICMP_HDR);

    ident = foicmpheader->ident;

    if ((file = fopen(foicmpheader->fileName, "a+")) != NULL )
    {
        position = ident;
        fseek (file, position, SEEK_SET);
        fwrite(data, 1, dataSize, file);
        fclose(file);
    }
}
